#include "filesystem.h"
#include <stdio.h>
#include "deviceconfig.h"
#include "drvSD.h"
#include "drvInternelFlash.h"
#include "util.h"

#define FS_SD_SECTOR_SIZE 512
#define FS_INTERNEL_SECTOR_SIZE 512

TX_THREAD FilexTaskCtrlBlock;
static uint64_t FilexTaskStack[2048];
void filexTaskEntry(ULONG thread_input);
extern CALL_CHAIN_S PeriodCallHead;
CALL_CHAIN_S FilesystemCallchainNode;
void filesystemPeriodCallback(void);

TX_EVENT_FLAGS_GROUP FsEventGroug;

//internel flash storage
FX_MEDIA InternelMedia;
static uint8_t InternelBuf[FS_INTERNEL_SECTOR_SIZE] __attribute__ ((aligned (32)));
//sd storage
FX_MEDIA SDMedia;
static uint8_t SDScratch[FS_SD_SECTOR_SIZE] __attribute__ ((aligned (32)));
static uint8_t SDBuf[FS_SD_SECTOR_SIZE] __attribute__ ((aligned (32)));
TX_SEMAPHORE SDWriteSem;
TX_SEMAPHORE SDReadSem;

void fsMountInternel(void);
void fsUnMountInternel(void);
void fxInternelDrv(FX_MEDIA *media_ptr);

void fsMountSD(void);
void fsUnMountSD(void);
void fxSDDrv(FX_MEDIA *media_ptr);
UINT _fx_partition_offset_calculate(void  *partition_sector, UINT partition, ULONG *partition_start, ULONG *partition_size);

void fsMount(FILESYSTEM_TYPE_E type);
void fsUnMount(FILESYSTEM_TYPE_E type);

void fsInit(void)
{
    FilesystemCallchainNode.name = "FilesystemCallchainNode";
    FilesystemCallchainNode.ptr = filesystemPeriodCallback;
    FilesystemCallchainNode.next = NULL;
    pushCallchain(&PeriodCallHead,&FilesystemCallchainNode);
    fx_system_initialize();
    //fsMount(FILESYSTEM_TYPE_INTERNEL);
}
void fsDeInit(void)
{
    //fsUnMount(FILESYSTEM_TYPE_INTERNEL);
}
void fsMount(FILESYSTEM_TYPE_E type)
{
    switch(type) {
        case FILESYSTEM_TYPE_INTERNEL:
            fsMountInternel();
            break;
        case FILESYSTEM_TYPE_SD:
            fsMountSD();
            break;
    }
}
void fsUnMount(FILESYSTEM_TYPE_E type)
{
    switch(type) {
        case FILESYSTEM_TYPE_INTERNEL:
            fsUnMountInternel();
            break;
        case FILESYSTEM_TYPE_SD:
            fsUnMountSD();
            break;
    }
}

void fsMountInternel(void)
{
    fx_media_open(&InternelMedia, "Internel", fxInternelDrv, 0, InternelBuf, sizeof(InternelBuf));
}
void fsUnMountInternel(void)
{
    fx_media_close(&InternelMedia);
}
void fxInternelDrv(FX_MEDIA *media_ptr) 
{
    switch (media_ptr->fx_media_driver_request) {
        case FX_DRIVER_READ: 
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;

        case FX_DRIVER_WRITE:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;

        case FX_DRIVER_FLUSH:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;

        case FX_DRIVER_ABORT:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;

        case FX_DRIVER_INIT:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break; 

        case FX_DRIVER_UNINIT:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;

        case FX_DRIVER_BOOT_READ:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;

        case FX_DRIVER_BOOT_WRITE:
            media_ptr -> fx_media_driver_status = FX_SUCCESS; 
            break;
        default: 
            media_ptr -> fx_media_driver_status = FX_IO_ERROR; 
            break;
    }
}

static int8_t fsCheckSDStatus()
{
    uint32_t start = tx_time_get();
    while (tx_time_get() - start < 1000) {
        if (drvSDGetCardState() == 0)
            return 0;
    }
    return -1;
}
void fsSDSemCtrl(SD_CTRL_E type, bool ctrl)
{
    if (type == SD_CTRL_READ) {
        ctrl == true ? tx_semaphore_get(&SDReadSem, 0) : tx_semaphore_put(&SDReadSem);    
    } else if (type == SD_CTRL_WRITE) {
        ctrl == true ? tx_semaphore_get(&SDWriteSem, 0) : tx_semaphore_put(&SDWriteSem);
    }
    
}
static uint32_t fsSDRead(FX_MEDIA *media_ptr, ULONG start_sector, UINT num_sectors)
{
    int32_t i = 0;
    uint32_t status = FX_IO_ERROR;

    memset(SDScratch, '\0', FS_SD_SECTOR_SIZE);
    for (i = 0; i < num_sectors; i++) {
        status = drvSDReadBlocks((uint32_t*)SDScratch, start_sector++, 1);
        if (status != 0) {
            status = FX_IO_ERROR;
            break;
        }

        if(tx_semaphore_get(&SDReadSem, 1000) != TX_SUCCESS) {
            status = FX_BUFFER_ERROR;
            break;
        } else {
            _fx_utility_memory_copy(SDScratch, media_ptr->fx_media_driver_buffer, FS_SD_SECTOR_SIZE);
            media_ptr->fx_media_driver_buffer += FS_SD_SECTOR_SIZE;
        }
    }

    if (i == num_sectors)
    {
        status = FX_SUCCESS;
    }
    
    return status;
}
static uint32_t fsSDWrite(FX_MEDIA *media_ptr, ULONG start_sector, UINT num_sectors)
{
    int32_t i = 0;
    uint32_t status = FX_BUFFER_ERROR;
    for (i = 0; i < num_sectors; i++) {
        _fx_utility_memory_copy(media_ptr->fx_media_driver_buffer, SDScratch, FS_SD_SECTOR_SIZE);
        media_ptr->fx_media_driver_buffer += FS_SD_SECTOR_SIZE;
        status = drvSDWriteBlocks((uint32_t*)SDScratch, (uint32_t)start_sector++, 1);
        if (status != 0) {
            status = FX_IO_ERROR;
            break;
        }
        if(tx_semaphore_get(&SDWriteSem, 1000) != TX_SUCCESS) {
            status = FX_BUFFER_ERROR;
            break;
        }
    }
    if (i == num_sectors)
        status = FX_SUCCESS;    
    return status;
}
void fxSDDrv(FX_MEDIA *media_ptr)
{
    int32_t status;
    ULONG partition_start;
    ULONG partition_size;
    switch(media_ptr->fx_media_driver_request) {
        case FX_DRIVER_INIT:            
            if (tx_semaphore_create(&SDReadSem, "", 0) != TX_SUCCESS || \
                tx_semaphore_create(&SDWriteSem, "", 0) != TX_SUCCESS) {
                media_ptr->fx_media_driver_status =  FX_IO_ERROR;
            } else {
                media_ptr->fx_media_driver_status =  FX_SUCCESS;
            }
            break;

        case FX_DRIVER_UNINIT:
            tx_semaphore_delete(&SDReadSem);
            tx_semaphore_delete(&SDWriteSem);
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;

        case FX_DRIVER_READ:
            media_ptr->fx_media_driver_status = FX_IO_ERROR;
            if (fsSDRead(media_ptr, \
                media_ptr->fx_media_driver_logical_sector + \
                media_ptr->fx_media_hidden_sectors, \
                media_ptr->fx_media_driver_sectors) == FX_SUCCESS) {
                media_ptr->fx_media_driver_status = FX_SUCCESS;
            }
            break;

        case FX_DRIVER_WRITE:
            media_ptr->fx_media_driver_status = FX_IO_ERROR;
            if(fsSDWrite(media_ptr, \
                media_ptr->fx_media_driver_logical_sector + \
                media_ptr->fx_media_hidden_sectors, \
                media_ptr->fx_media_driver_sectors) == FX_SUCCESS) {
                media_ptr->fx_media_driver_status = FX_SUCCESS;
            }
            break;

        case FX_DRIVER_FLUSH:
            media_ptr->fx_media_driver_status =  FX_SUCCESS;
            break;

        case FX_DRIVER_ABORT:
            media_ptr->fx_media_driver_status =  FX_SUCCESS;
            break;
        
        case FX_DRIVER_BOOT_READ: 
            status = fsSDRead(media_ptr, 0, media_ptr->fx_media_driver_sectors);
            if (status != FX_SUCCESS) {
                media_ptr->fx_media_driver_status = FX_IO_ERROR;
                break;
            }
            partition_start =  0;
            status = _fx_partition_offset_calculate(media_ptr->fx_media_driver_buffer, 0,
                &partition_start, &partition_size);

            if (status) {
                media_ptr -> fx_media_driver_status =  FX_IO_ERROR;
                break;
            }
            if (partition_start) {
                if (fsCheckSDStatus() != 0) {
                    media_ptr->fx_media_driver_status =  FX_IO_ERROR;
                    break;
                }
                status = fsSDRead(media_ptr, partition_start, media_ptr->fx_media_driver_sectors);
                if (status != FX_SUCCESS) {
                    media_ptr->fx_media_driver_status = FX_IO_ERROR;
                    break;
                }
            }
            media_ptr->fx_media_driver_status =  FX_SUCCESS;
            break;

        case FX_DRIVER_BOOT_WRITE:
            status = fsSDWrite(media_ptr, 0, media_ptr->fx_media_driver_sectors);
            media_ptr->fx_media_driver_status = status;
            break;

        default:
            media_ptr->fx_media_driver_status =  FX_IO_ERROR;
            break;
    }
}
void fsMountSD(void)
{
    fx_media_open(&SDMedia, "SD", fxSDDrv, 0, SDBuf, sizeof(SDBuf));
}
void fsUnMountSD(void)
{
    fx_media_close(&SDMedia);
}

void filexTaskInit(void)
{
    fsInit();
    tx_event_flags_create(&FsEventGroug, "FsEventGroug");
    tx_thread_create(&FilexTaskCtrlBlock,
        "",
        filexTaskEntry,
        0,
        FilexTaskStack, 
        sizeof(FilexTaskStack),
        TX_PRIORITIE_GUIX,
        TX_PRIORITIE_GUIX,
        TX_NO_TIME_SLICE,
        TX_AUTO_START);
}

void filexTaskEntry(ULONG thread_input)
{    
    FS_EVENT_U eventflagret;
    FS_EVENT_U waitevent;
    waitevent.bit.FS_EVENT_SD_PLUG = 1;
    waitevent.bit.FS_EVENT_SD_UNPLUG = 1;
    while(1) {
        if(tx_event_flags_get(&FsEventGroug,\
            waitevent.evnet,
            TX_OR_CLEAR, \
            &(eventflagret.evnet), \
            TX_NO_WAIT) == TX_SUCCESS) {
            if(eventflagret.bit.FS_EVENT_SD_PLUG) {
                fsMount(FILESYSTEM_TYPE_SD);
            }
            if(eventflagret.bit.FS_EVENT_SD_UNPLUG) {
                fsUnMount(FILESYSTEM_TYPE_SD);
            }
        }
        printf("task file\r\n");
        tx_thread_sleep(1000);
    }
}

void filesystemPeriodCallback(void)
{
    SD_DECT_E state = drvSDDetected();
    FS_EVENT_U eventtoset;
    eventtoset.evnet = 0;
    if (state == SD_DECT_PLUG) {
        eventtoset.bit.FS_EVENT_SD_PLUG = 1;
        tx_event_flags_set(&FsEventGroug,\
            eventtoset.evnet,
            TX_OR);
    } else if (state == SD_DECT_UNPLUG) {
        eventtoset.bit.FS_EVENT_SD_UNPLUG = 1;
        tx_event_flags_set(&FsEventGroug,\
            eventtoset.evnet,
            TX_OR);
    }
}